﻿using System.Collections;
using System.Collections.Generic;

namespace Borg.Framework
{
    public class ObjectCollection<TKey, TValue> : IDictionary<TKey, TValue>
    {
        internal readonly Dictionary<TKey, TValue> dictionary;
        internal readonly List<KeyValuePair<TKey, TValue>> pairList;

        internal event ItemHandler Added;
        internal event ItemHandler Cleared;

        internal event IndexItemHandler Inserted;

        internal event ItemHandler Removed;

        internal event IndexItemHandler RemovedAt;
        internal event IndexItemHandler ReplacedAt;

        internal ObjectCollection()
        {
            pairList = new List<KeyValuePair<TKey, TValue>>();
            dictionary = new Dictionary<TKey, TValue>();
        }

        public void Add(TKey key, TValue value)
        {
            dictionary.Add(key, value);
            var kvPair = new KeyValuePair<TKey, TValue>(key, value);
            pairList.Add(new KeyValuePair<TKey, TValue>(key, value));
            OnAdded(kvPair);
        }

        public void Clear()
        {
            dictionary.Clear();
            pairList.Clear();
            OnCleared();
        }

        public bool ContainsKey(TKey key)
        {
            return dictionary.ContainsKey(key);
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return pairList.GetEnumerator();
        }

        public int IndexOf(TKey key)
        {
            for (var i = 0; i < pairList.Count; i++)
            {
                var kvPair = pairList[i];

                if (key.Equals(kvPair.Key))
                    return i;
            }

            return -1;
        }

        public void Insert(int index, TKey key, TValue value)
        {
            dictionary.Add(key, value);

            var kvPair = new KeyValuePair<TKey, TValue>(key, value);
            
            pairList.Insert(index, kvPair);
            OnInserted(index, kvPair);
        }

        private void OnAdded(KeyValuePair<TKey, TValue> item)
        {
            if (Added != null)
                Added(item);
        }

        private void OnCleared()
        {
            if (Cleared != null)
                Cleared(new KeyValuePair<TKey, TValue>());
        }

        private void OnInserted(int index, KeyValuePair<TKey, TValue> item)
        {
            if (Inserted != null)
                Inserted(index, item);
        }

        private void OnRemoved(KeyValuePair<TKey, TValue> item)
        {
            if (Removed != null)
                Removed(item);
        }

        private void OnRemovedAt(int index, KeyValuePair<TKey, TValue> item)
        {
            if (RemovedAt != null)
                RemovedAt(index, item);
        }

        private void OnReplacedAt(int index, KeyValuePair<TKey, TValue> item)
        {
            if (ReplacedAt != null)
                ReplacedAt(index, item);
        }

        public bool Remove(TKey key)
        {
            if (dictionary.ContainsKey(key))
            {
                for (var i = 0; i < pairList.Count; i++)
                {
                    var kvPair = pairList[i];

                    if (!kvPair.Key.Equals(key))
                        continue;

                    var item = pairList[i];

                    pairList.RemoveAt(i);
                    dictionary.Remove(key);
                    OnRemovedAt(i, item);

                    return true;
                }
            }

            return false;
        }

        public void RemoveAt(int index)
        {
            var kvPair = pairList[index];

            dictionary.Remove(kvPair.Key);
            pairList.RemoveAt(index);
            OnRemovedAt(index, kvPair);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            Add(item.Key, item.Value);
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
        {
            return dictionary.ContainsKey(item.Key);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            pairList.CopyTo(array, arrayIndex);
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            return Remove(item.Key);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            return dictionary.TryGetValue(key, out value);
        }

        public int Count
        {
            get { return pairList.Count; }
        }

        public KeyValuePair<TKey, TValue> this[int index]
        {
            get
            {
                return index >= 0 && index < pairList.Count
                    ? pairList[index]
                    : new KeyValuePair<TKey, TValue>();
            }
        }

        public ICollection<TKey> Keys
        {
            get { return dictionary.Keys; }
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
        {
            get { return false; }
        }

        TValue IDictionary<TKey, TValue>.this[TKey key]
        {
            get { return dictionary[key]; }
            set
            {
                var kvPair = new KeyValuePair<TKey, TValue>(key, value);

                if (dictionary.ContainsKey(key))
                {
                    var index = IndexOf(key);
                    pairList[index] = kvPair;
                    dictionary[key] = value;
                    OnReplacedAt(index, kvPair);
                }
                else
                {
                    pairList.Add(kvPair);
                    dictionary[key] = value;
                    OnAdded(kvPair);
                }
            }
        }

        public ICollection<TValue> Values
        {
            get { return dictionary.Values; }
        }

        internal delegate void IndexItemHandler(int index, KeyValuePair<TKey, TValue> item);

        internal delegate void ItemHandler(KeyValuePair<TKey, TValue> item);
    }
}

